home *** CD-ROM | disk | FTP | other *** search
/ Giga Games 1 / Giga Games.iso / net / hack / 3_1_3 / sys / vms / vmsmail.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-06-27  |  16.1 KB  |  474 lines

  1. /*    SCCS Id: @(#)vmsmail.c    3.1    93/06/22    */
  2. /* Copyright (c) Robert Patrick Rankin, 1991.              */
  3. /* NetHack may be freely redistributed.  See license for details. */
  4.  
  5. #define EXTERN_H    /* don't need all those prototypes */
  6. #include "config.h"
  7.  
  8. #ifdef MAIL
  9. #include "winprocs.h"
  10. #include "mail.h"
  11. #include <ctype.h>
  12. #include <descrip.h>
  13. #include <errno.h>
  14. # ifndef __GNUC__
  15. #include <msgdef.h>
  16. # else
  17. #  define MSG$_TRMHANGUP  6
  18. #  define MSG$_TRMBRDCST 83
  19. # endif /*__GNUC__*/
  20. #include <signal.h>
  21. /* #include <string.h> */
  22. # define vms_ok(sts) ((sts)&1)
  23.  
  24. static struct mail_info *FDECL(parse_brdcst, (char *));
  25. static void FDECL(filter_brdcst, (char *));
  26. static void NDECL(flush_broadcasts);
  27. static void FDECL(broadcast_ast, (int));
  28. extern char *strcpy(), *strcat(), *strrchr(), *strstri(), *eos();
  29. extern int strspn(), strncmpi();
  30. #ifndef __DECC
  31. extern int VDECL(sscanf, (const char *,const char *,...));
  32. #endif
  33. extern unsigned long
  34.     SMG$CREATE_PASTEBOARD(),
  35.     SMG$GET_BROADCAST_MESSAGE(),
  36.     SMG$SET_BROADCAST_TRAPPING(),
  37.     SMG$DISABLE_BROADCAST_TRAPPING();
  38.  
  39. extern volatile int broadcasts;        /* defining declaration in mail.c */
  40.  
  41. static long pasteboard_id = 0;        /* SMG's magic cookie */
  42.  
  43. /*
  44.  * Mail (et al) overview:
  45.  *
  46.  *    When a broadcast is asynchronously captured, a volatile counter
  47.  * ('broadcasts') is incremented.  Each player turn, ckmailstatus() polls
  48.  * the counter and calls parse_next_broadcast() if it's positive; this
  49.  * returns some display text, object name, and response command, which is
  50.  * passed to newmail().  Routine newmail() generates a mail-daemon monster
  51.  * who approaches the character, "speaks" the display text, and delivers
  52.  * a scroll of mail pre-named to the object name; the response command is
  53.  * initially appended to the name, so that the object is tagged with both
  54.  * of them; a NUL is inserted to terminate the ordinary name and hide the
  55.  * command.  (If the player renames such a scroll, the hidden command will
  56.  * be lost; who cares?)  Unrecognized broadcasts result in the mail-daemon
  57.  * arriving and announcing the display text, but no scroll being created.
  58.  * If SHELL is undefined, then all broadcasts are treated as 'other'; since
  59.  * no subproceses are allowed, there'd be no way to respond to the scroll.
  60.  *
  61.  *    When a scroll of mail is read by the character, readmail() extracts
  62.  * the hidden command string and uses it for the default when prompting the
  63.  * player for a system command to spawn.  The player may enter any command
  64.  * he or she chooses, or just <return> to accept the default or <escape> to
  65.  * avoid executing any command.  If the command is "SPAWN", a regular shell
  66.  * escape to DCL is performed; otherwise, the indicated single command is
  67.  * spawned.  Either way, NetHack resumes play when the subprocess terminates
  68.  * or explicitly reattaches to its parent.
  69.  *
  70.  * Broadcast parsing:
  71.  *
  72.  *    The following broadcast messages are [attempted to be] recognized:
  73.  *    text fragment          name for scroll          default command
  74.  *    New mail        VMSmail            MAIL
  75.  *    New ALL-IN-1 MAIL    A1mail            A1M
  76.  *    Software Tools mail    STmail            MSG [+folder]
  77.  *    MM mail            MMmail            MM
  78.  *    WPmail: New mail    WPmail            OFFICE/MAIL
  79.  *    **M400 mail        M400mail        M400
  80.  *    " mail", ^"mail "    unknown mail        SPAWN
  81.  *    " phoning"        Phone call        PHONE ANSWER
  82.  *    talk-daemon...by...foo    Talk request        TALK[/OLD] foo@bar
  83.  *    (node)user -        Bitnet noise        XYZZY user@node
  84.  * Anything else results in just the message text being passed along, no
  85.  * scroll of mail so consequently no command to execute when scroll read.
  86.  * The user can set up ``$ XYZZY :== SEND'' prior to invoking NetHack if
  87.  * vanilla JNET responses to Bitnet messages are prefered.
  88.  *
  89.  *    Static return buffers are used because only one broadcast gets
  90.  * processed at a time, and the essential information in each one is
  91.  * either displayed and discarded or copied into a scroll-of-mail object.
  92.  *
  93.  *    The test driver code below can be used to check out potential new
  94.  * entries without rebuilding NetHack itself.  CC/DEFINE="TEST_DRIVER"
  95.  * Link it with hacklib.obj or nethack.olb/incl=hacklib (not nethack/lib).
  96.  */
  97.  
  98. static struct mail_info msg;    /* parse_*()'s return buffer */
  99. static char nam_cmd_buf[63],    /* maximum onamelth, size of ONAME(object) */
  100.         txt_buf[255+1];    /* same size as used for message buf[] */
  101.  
  102. /* try to decipher and categorize broadcast message text
  103. */
  104. static struct mail_info *
  105. parse_brdcst(buf)        /* called by parse_next_broadcast() */
  106. char *buf;            /* input: filtered broadcast text */
  107. {
  108.     int typ;
  109.     char *txt;
  110.     const char *nam, *cmd;
  111. # ifdef SHELL        /* only parse if spawned commands are enabled */
  112.     register char *p, *q;
  113.     boolean is_jnet_send;
  114.     char cmd_buf[127+1], user[127+1], node[127+1], sentinel;
  115.  
  116.     /* Check these first; otherwise, their arbitrary text would enable
  117.     easy spoofing of some other message patterns.  Unfortunately,
  118.     any home-grown broadcast delivery program poses a similar risk. */
  119.     if (!strncmpi(buf, "reply received", 14)) goto other;
  120.     is_jnet_send = (sscanf(buf, "(%[^)])%s -%c", node, user, &sentinel) == 3);
  121.     if (is_jnet_send) goto jnet_send;
  122.  
  123.     /* scan the text more or less by brute force */
  124.     if ((q = strstri(buf, " mail")) != 0 ||    /* all known mail broadcasts */
  125.     !strncmpi(q = buf, "mail ", 5)) {    /* unexpected alternative */
  126.     typ = MSG_MAIL;
  127.     p = strstri(q, " from");
  128.     txt = p ? strcat(strcpy(txt_buf, "Mail for you"), p) : (char *) 0;
  129.  
  130.     if (!strncmpi(buf, "new mail", 8)) {
  131. /*
  132. New mail [on node FOO] from [SPAM::]BAR [\"personal_name\"] [\(HH:MM:SS\)]
  133. */
  134.         nam = "VMSmail";        /* assume VMSmail */
  135.         cmd = "MAIL";
  136.         if (txt && (p = strrchr(txt, '(')) > txt && /* discard time */
  137.         (--p, strspn(p, "0123456789( :.)") == strlen(p))) *p = '\0';
  138.     } else if (!strncmpi(buf, "new all-in-1", 12)) {
  139.         int i;
  140. /*
  141. New ALL-IN-1 MAIL message [on node FOO] from Personal Name \(BAR@SPAM\) [\(DD-MMM-YYYY HH:MM:SS\)]
  142. */
  143.         nam = "A1mail";
  144.         cmd = "A1M";
  145.         if (txt && (p = strrchr(txt, '(')) > txt && /* discard date+time */
  146.         sscanf(p-1," (%*d-%*[^-]-%*d %*d:%*d:%d) %c",&i,&sentinel) == 1)
  147.         *--p = '\0';
  148.     } else if (!strncmpi(buf, "software tools", 14)) {
  149. /*
  150. Software Tools mail has arrived on FOO from \'BAR\' [in SPAM]
  151. */
  152.         nam = "STmail";
  153.         cmd = "MSG";
  154.         if (txt && (p = strstri(p, " in ")) != 0)    /* specific folder */
  155.         cmd = strcat(strcpy(cmd_buf, "MSG +"), p + 4);
  156.     } else if (q - 2 >= buf && !strncmpi(q - 2, "mm", 2)) {
  157. /*
  158. {MultiNet\ |PMDF\/}MM mail has arrived on FOO from BAR\n
  159. [Subject: subject_text] (PMDF only)
  160. */
  161.         nam = "MMmail";        /* MultiNet's version of MM */
  162.         cmd = "MM";            /*{ perhaps "MM READ"? }*/
  163.     } else if (!strncmpi(buf, "wpmail:", 7)) {
  164. /*
  165. WPmail: New mail from BAR.  subject_text
  166. */
  167.         nam = "WPmail";        /* WordPerfect [sic] Office */
  168.         cmd = "OFFICE/MAIL";
  169.     } else if (!strncmpi(buf, "**m400 mail", 7)) {
  170. /*
  171. **M400 mail waiting**
  172. */
  173.         nam = "M400mail";        /* Messenger 400 [not seen] */
  174.         cmd = "M400";
  175.     } else {
  176.         /* not recognized, but presumed to be mail */
  177.         nam = "unknown mail";
  178.         cmd = "SPAWN";        /* generic escape back to DCL */
  179.         txt = (char *) 0;        /* don't rely on "from" info here */
  180.     }
  181.  
  182.     if (!txt) txt = strcat(strcpy(txt_buf, "Mail for you: "), buf);
  183.     /*
  184.      :    end of mail recognition; now check for call-type interruptions...
  185.      */
  186.     } else if ((q = strstri(buf, " phoning")) != 0) {
  187. /*
  188. BAR is phoning you [on FOO] \(HH:MM:SS\)
  189. */
  190.     typ = MSG_CALL;
  191.     nam = "Phone call";
  192.     cmd = "PHONE ANSWER";
  193.     if (!strncmpi(q + 8, " you", 4)) q += (8 + 4), *q = '\0';
  194.     txt = strcat(strcpy(txt_buf, "Do you hear ringing?  "), buf);
  195.     } else if ((q = strstri(buf, " talk-daemon")) != 0 ||
  196.            (q = strstri(buf, " talk_daemon")) != 0) {
  197. /*
  198. Message from TALK-DAEMON@FOO at HH:MM:SS\n
  199. Connection request by BAR@SPAM\n
  200. \[Respond with: TALK[/OLD] BAR@SPAM\]
  201. */
  202.     typ = MSG_CALL;
  203.     nam = "Talk request";        /* MultiNet's TALK and/or TALK/OLD */
  204.     cmd = "TALK";
  205.     if ((p = strstri(q, " by ")) != 0) {
  206.         txt = strcat(strcpy(txt_buf, "Talk request from"), p + 3);
  207.         if ((p = strstri(p, "respond with")) != 0) {
  208.         if (*(p-1) == '[') *(p-1) = '\0'; else *p = '\0'; /* terminate */
  209.         p += (sizeof "respond with" - sizeof "");
  210.         if (*p == ':') p++;
  211.         if (*p == ' ') p++;
  212.         cmd = strcpy(cmd_buf, p);    /* "TALK[/OLD] bar@spam" */
  213.         p = eos(cmd);
  214.         if (*--p == ']') *p = '\0';
  215.         }
  216.     } else
  217.         txt = strcat(strcpy(txt_buf, "Pardon the interruption: "), buf);
  218.     } else if (is_jnet_send) {    /* sscanf(,"(%[^)])%s -%c",,,)==3 */
  219. jnet_send:
  220. /*
  221. \(SPAM\)BAR - arbitrary_message_text (from BAR@SPAM)
  222. */
  223.     typ = MSG_CALL;
  224.     nam = "Bitnet noise";        /* RSCS/NJE message received via JNET */
  225.     Sprintf(cmd_buf, "XYZZY %s@%s", user, node);
  226.     cmd = cmd_buf;
  227.     /*{ perhaps just vanilla SEND instead of XYZZY? }*/
  228.     Sprintf(txt_buf, "Message from %s@%s:%s", user, node,
  229.         &buf[1+strlen(node)+1+strlen(user)+2-1]);  /* "(node)user -" */
  230.     txt = txt_buf;
  231.     /*
  232.      :    end of call recognition; anything else is none-of-the-above...
  233.      */
  234.     } else {
  235. other:
  236. # endif /* SHELL */
  237. /* arbitrary broadcast: batch job completed, system shutdown imminent, &c */
  238.     typ = MSG_OTHER;
  239.     nam = (char *) 0; /*"captured broadcast message"*/
  240.     cmd = (char *) 0;
  241.     txt = strcat(strcpy(txt_buf, "Message for you: "), buf);
  242. # ifdef SHELL
  243.     }
  244.     /* Daemon in newmail() will append period when the text is displayed */
  245.     if ((p = eos(txt)) > txt && *--p == '.') *p = '\0';
  246.  
  247.     /* newmail() and readmail() assume that nam and cmd are concatenated */
  248.     if (nam) {        /* object name to attach to scroll of mail */
  249.     char *join = strcpy(nam_cmd_buf, nam);
  250.     if (cmd) {    /* append command to name; readmail() requires it */
  251.         int len = sizeof nam_cmd_buf - sizeof "" - (strlen(join) + 1);
  252.         cmd_buf[len] = '\0';    /* possibly truncate */
  253.         (void) strcat(join, " ");
  254.         cmd = strcpy(eos(join), cmd);
  255.     }
  256.     nam = join;
  257.     }
  258. # endif /* SHELL */
  259.     /* truncate really long messages to prevent verbalize() from blowing up */
  260.     if (txt && strlen(txt) > BUFSZ - 50) txt[BUFSZ - 50] = '\0';
  261.  
  262.     msg.message_typ  = typ;    /* simple index */
  263.     msg.display_txt  = txt;    /* text for daemon to pline() */
  264.     msg.object_nam   = nam;    /* 'name' for mail scroll */
  265.     msg.response_cmd = cmd;    /* command to spawn when scroll read */
  266.     return &msg;
  267. }
  268.  
  269. /* filter out non-printable characters and redundant noise
  270. */
  271. static void
  272. filter_brdcst(buf)        /* called by parse_next_broadcast() */
  273. register char *buf;        /* in: original text; out: filtered text */
  274. {
  275.     register char c, *p, *buf_p;
  276.  
  277.     /* filter the text; restrict consecutive spaces or dots to just two */
  278.     for (p = buf_p = buf; *buf_p; buf_p++) {
  279.     c = *buf_p & '\177';
  280.     if (c == ' ' || c == '\t' || c == '\n')
  281.         if (p == buf ||        /* ignore leading whitespace */
  282.         (p >= buf+2 && *(p-1) == ' ' && *(p-2) == ' ')) continue;
  283.         else c = ' ';
  284.     else if (c == '.' || c < ' ' || c == '\177')
  285.         if (p == buf ||        /* skip leading beeps & such */
  286.         (p >= buf+2 && *(p-1) == '.' && *(p-2) == '.')) continue;
  287.         else c = '.';
  288.     else if (c == '%' &&        /* trim %%% OPCOM verbosity %%% */
  289.         p >= buf+2 && *(p-1) == '%' && *(p-2) == '%') continue;
  290.     *p++ = c;
  291.     }
  292.     *p = '\0';            /* terminate, then strip trailing junk */
  293.     while (p > buf && (*--p == ' ' || *p == '.')) *p = '\0';
  294.     return;
  295. }
  296.  
  297. /* fetch the text of a captured broadcast, then mangle and decipher it
  298. */
  299. struct mail_info *
  300. parse_next_broadcast()        /* called by ckmailstatus(mail.c) */
  301. {
  302.     short length, msg_type;
  303.     $DESCRIPTOR(message, "");    /* string descriptor for buf[] */
  304.     struct mail_info *result = 0;
  305.     /* messages could actually be longer; let long ones be truncated */
  306.     char buf[255+1];
  307.  
  308.     message.dsc$a_pointer = buf,  message.dsc$w_length = sizeof buf - 1;
  309.     msg_type = length = 0;
  310.     SMG$GET_BROADCAST_MESSAGE(&pasteboard_id, &message, &length, &msg_type);
  311.     if (msg_type == MSG$_TRMBRDCST) {
  312.     buf[length] = '\0';
  313.     filter_brdcst(buf);        /* mask non-printable characters */
  314.     result = parse_brdcst(buf);    /* do the real work */
  315.     } else if (msg_type == MSG$_TRMHANGUP) {
  316.     (void) gsignal(SIGHUP);
  317.     }
  318.     return result;
  319. }
  320.  
  321. /* spit out any pending broadcast messages whenever we leave
  322. */
  323. static void
  324. flush_broadcasts()    /* called from disable_broadcast_trapping() */
  325. {
  326.     if (broadcasts > 0) {
  327.     short len, typ;
  328.     $DESCRIPTOR(msg_dsc, "");
  329.     char buf[512+1];
  330.  
  331.     msg_dsc.dsc$a_pointer = buf,  msg_dsc.dsc$w_length = sizeof buf - 1;
  332.     raw_print("");        /* print at least one line for wait_synch() */
  333.     do {
  334.         typ = len = 0;
  335.         SMG$GET_BROADCAST_MESSAGE(&pasteboard_id, &msg_dsc, &len, &typ);
  336.         if (typ == MSG$_TRMBRDCST) buf[len] = '\0',  raw_print(buf);
  337.     } while (--broadcasts);
  338.     wait_synch();        /* prompt with "Hit return to continue: " */
  339.     }
  340. }
  341.  
  342. /* AST routine called when the terminal's associated mailbox receives a message
  343. */
  344. static void
  345. broadcast_ast(dummy)        /* called asynchronously by terminal driver */
  346. int dummy;    /* not used */
  347. {
  348.     broadcasts++;
  349. }
  350.  
  351. /* initialize the broadcast manipulation code; SMG makes this easy
  352. */
  353. unsigned long init_broadcast_trapping()   /* called by setftty() [once only] */
  354. {
  355.     unsigned long sts, preserve_screen_flag = 1;
  356.  
  357.     /* we need a pasteboard to pass to the broadcast setup/teardown routines */
  358.     sts = SMG$CREATE_PASTEBOARD(&pasteboard_id, 0, 0, 0, &preserve_screen_flag);
  359.     if (!vms_ok(sts)) {
  360.     errno = EVMSERR,  vaxc$errno = sts;
  361.     raw_print("");
  362.     perror("?can't create SMG pasteboard for broadcast trapping");
  363.     wait_synch();
  364.     broadcasts = -1;    /* flag that trapping is currently broken */
  365.     }
  366.     return sts;
  367. }
  368.  
  369. /* set up the terminal driver to deliver $brkthru data to a mailbox device
  370. */
  371. unsigned long enable_broadcast_trapping()    /* called by setftty() */
  372. {
  373.     unsigned long sts = 1;
  374.  
  375.     if (broadcasts >= 0) {    /* (-1 => no pasteboard, so don't even try) */
  376.     /* register callback routine to be triggered when broadcasts arrive */
  377.     /* Note side effect:  also intercepts hangup notification. */
  378.     /* Another note:  TMPMBX privilege is required. */
  379.     sts = SMG$SET_BROADCAST_TRAPPING(&pasteboard_id, broadcast_ast, 0);
  380.     if (!vms_ok(sts)) {
  381.         errno = EVMSERR,  vaxc$errno = sts;
  382.         raw_print("");
  383.         perror("?can't enable broadcast trapping");
  384.         wait_synch();
  385.     }
  386.     }
  387.     return sts;
  388. }
  389.  
  390. /* return to 'normal'; $brkthru data goes straight to the terminal
  391. */
  392. unsigned long disable_broadcast_trapping()    /* called by settty() */
  393. {
  394.     unsigned long sts = 1;
  395.  
  396.     if (broadcasts >= 0) {
  397.     /* disable trapping; releases associated MBX so that SPAWN can work */
  398.     sts = SMG$DISABLE_BROADCAST_TRAPPING(&pasteboard_id);
  399.     if (!vms_ok(sts)) errno = EVMSERR,  vaxc$errno = sts;
  400.     flush_broadcasts();    /* don't hold on to any buffered ones */
  401.     }
  402.     return sts;
  403. }
  404. #else    /* MAIL */
  405.     /* simple stubs for non-mail configuration */
  406. unsigned long init_broadcast_trapping() { return 1; }
  407. unsigned long enable_broadcast_trapping() { return 1; }
  408. unsigned long disable_broadcast_trapping() { return 1; }
  409. #endif    /* MAIL */
  410.  
  411. /*----------------------------------------------------------------------*/
  412.  
  413. #ifdef TEST_DRIVER
  414.     /* (Take parse_next_broadcast for a spin. :-) */
  415.  
  416. volatile int broadcasts = 0;
  417.  
  418. void newmail(foo)
  419. struct mail_info *foo;
  420. {
  421. # define STRING(s) ((s) ? (s) : "<null>")
  422.     printf("\n\
  423.   message type = %d\n\
  424.   display text = \"%s\"\n\
  425.   object name  = \"%.*s\"\n\
  426.   response cmd = \"%s\"\n\
  427. ",      foo->message_typ, STRING(foo->display_txt),
  428.     (foo->object_nam && foo->response_cmd) ?
  429.         (foo->response_cmd - foo->object_nam - 1) :
  430.         strlen(STRING(foo->object_nam)),
  431.     STRING(foo->object_nam), STRING(foo->response_cmd));
  432. # undef STRING
  433. }
  434.  
  435. void ckmailstatus()
  436. {
  437.     struct mail_info *brdcst, *parse_next_broadcast();
  438.  
  439.     while (broadcasts > 0) {    /* process all trapped broadcasts [until] */
  440.     broadcasts--;
  441.     if ((brdcst = parse_next_broadcast()) != 0) {
  442.         newmail(brdcst);
  443.         break;        /* only handle one real message at a time */
  444.     } else
  445.         printf("\n--< non-broadcast encountered >--\n");
  446.     }
  447. }
  448.  
  449. int main()
  450. {
  451.     char dummy[BUFSIZ];
  452.  
  453.     init_broadcast_trapping();
  454.     enable_broadcast_trapping();
  455.     for (;;) {
  456.     ckmailstatus();
  457.     printf("> "), fflush(stdout);    /* issue a prompt */
  458.     if (!gets(dummy)) break;    /* wait for a response */
  459.     }
  460.     disable_broadcast_trapping();
  461.     return 1;
  462. }
  463.  
  464. void panic(s) char *s; { raw_print(s); exit(1); }
  465.  
  466. void raw_print(s) char *s; { puts(s); fflush(stdout); }
  467.  
  468. void wait_synch() { char dummy[BUFSIZ];
  469.   printf("\nPress <return> to continue: "); fflush(stdout); (void) gets(dummy);
  470. }
  471. #endif    /* TEST_DRIVER */
  472.  
  473. /*vmsmail.c*/
  474.